#pragma once
#include <iostream>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <queue>

const int GthreadNum = 5;
template<class T>
class ThreadPool{
private:
  ThreadPool(const int threadNum = GthreadNum):_threadNum(threadNum)
  {
    pthread_mutex_init(&_mutex, NULL);
    pthread_cond_init(&_cond, NULL);
  }
public:
  ~ThreadPool()
  {
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
  }
  ThreadPool(const ThreadPool<T> &)  = delete;
  T& operator=(const ThreadPool<T> &) = delete;
public:
  static ThreadPool<T> *getInstance()
  {
    if(nullptr == instance)
    {
      if(nullptr == instance)
      {
        instance = new ThreadPool<T>();

        return instance;
      }
    }
    return nullptr;
  }
public:
  void start()
  {
    for(int i = 0; i < _threadNum; i++)
    {
      pthread_t t;
      pthread_create(&t, NULL, threadRoutine, (void*)this);
    }
    std::cout << "threadpool is ready" << std::endl;
  }
public:
  static void* threadRoutine(void* argv)
  {
    pthread_detach(pthread_self());
    ThreadPool<T> *tp = (ThreadPool<T>*)argv;

    while(true)
    {
      tp->lockQueue();
      while(!tp->haveTask())
      {
        tp->waitForTask();
      }
      T task = tp->pop();
      tp->unlockQueue();

      std::cout << "assignment success" << task.run() << std::endl;

    }//while
  }

  void push(T in)
  {
    lockQueue();
    _taskQueue.push(in);
    chooseThreadForHandler();
    unlockQueue();
  }

  T pop()
  {
    //lockQueue();
    T i = _taskQueue.front();
    _taskQueue.pop();
    //unlockQueue();
    return i;
  }
private:
  void lockQueue(){ pthread_mutex_lock(&_mutex); }
  void unlockQueue(){ pthread_mutex_unlock(&_mutex); }
  bool haveTask(){ return !_taskQueue.empty(); }
  void chooseThreadForHandler(){ pthread_cond_signal(&_cond); }
  void waitForTask(){ pthread_cond_wait(&_cond, &_mutex); }
private:
  int _threadNum;
  std::queue<T> _taskQueue;
  pthread_mutex_t _mutex;
  pthread_cond_t _cond;
  static ThreadPool<T> *instance;
};

template<class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;
